﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PickerSelector.cs" company="">
//   
// </copyright>
// <summary>
//   The picker selector.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace CustomDatePicker.Controls.DatePicker
{
    using System;
    using System.Diagnostics.CodeAnalysis;

    using CustomDatePicker.Controls.DatePicker.Common;

    using Windows.ApplicationModel;
    using Windows.Foundation;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Input;

    /// <summary>
    ///     The picker selector.
    /// </summary>
    [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(PickerSelectorItem))]
    public class PickerSelector : ItemsControl
    {
        #region Private Fields

        /// <summary>
        ///     The items panel.
        /// </summary>
        private LoopItemsPanel itemsPanel;

        /// <summary>
        ///     The selected item.
        /// </summary>
        private DateTimeWrapper selectedItem;

        /// <summary>
        ///     The selected picker selector item.
        /// </summary>
        private PickerSelectorItem selectedPickerSelectorItem;

        #endregion

        #region Constructor

        /// <summary>
        ///     Initializes a new instance of the <see cref="PickerSelector" /> class.
        ///     Ctor. Register for first LayoutUpdate to set initial DateTime
        /// </summary>
        public PickerSelector()
        {
            if (DesignMode.DesignModeEnabled)
            {
                return;
            }

            // Set style
            this.DefaultStyleKey = typeof(PickerSelector);
        }

        #endregion

        #region Internal Properties

        /// <summary>
        ///     Gets or sets the parent date picker.
        /// </summary>
        internal DatePicker DatePicker
        {
            get;
            set;
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///     Using a DependencyProperty as the backing store for isFocused.  This enables animation, styling, binding, etc.
        /// </summary>
        public static readonly DependencyProperty IsFocusedProperty = DependencyProperty.Register(
            "IsFocused",
            typeof(bool),
            typeof(PickerSelector),
            new PropertyMetadata(false, OnIsFocusedChanged));

        // Type of listbox (Day, Month, Year)

        /// <summary>
        ///     Gets or sets the data source type.
        /// </summary>
        public DataSourceType DataSourceType
        {
            get;
            set;
        }

        /// <summary>
        ///     Gets or sets the year data source.
        /// </summary>
        public YearDataSource YearDataSource
        {
            get;
            set;
        }

        /// <summary>
        ///     Gets or sets the month data source.
        /// </summary>
        public MonthDataSource MonthDataSource
        {
            get;
            set;
        }

        /// <summary>
        ///     Gets or sets the day data source.
        /// </summary>
        public DayDataSource DayDataSource
        {
            get;
            set;
        }

        /// <summary>
        ///     Gets or sets the rect position.
        /// </summary>
        public Rect RectPosition
        {
            get;
            set;
        }

        /// <summary>
        ///     Gets or sets a value indicating whether is focused.
        /// </summary>
        public bool IsFocused
        {
            get
            {
                return (bool)this.GetValue(IsFocusedProperty);
            }

            set
            {
                this.SetValue(IsFocusedProperty, value);
            }
        }

        /// <summary>
        ///     Gets or sets Current DateTime selected
        /// </summary>
        public DateTimeWrapper SelectedItem
        {
            get
            {
                return this.selectedItem;
            }

            set
            {
                this.selectedItem = value;

                // update the IsSelected items state
                this.UpdateIsSelectedItems(value);
            }
        }

        #endregion

        #region Internal Methods

        /// <summary>
        ///     The get selected picker selector item.
        /// </summary>
        /// <returns>
        ///     The <see cref="PickerSelectorItem" />.
        /// </returns>
        internal PickerSelectorItem GetSelectedPickerSelectorItem()
        {
            return this.selectedPickerSelectorItem;
        }

        /// <summary>
        ///     Return ItemsPanel
        /// </summary>
        /// <returns>
        ///     The <see cref="LoopItemsPanel" />.
        /// </returns>
        internal LoopItemsPanel GetItemsPanel()
        {
            return this.itemsPanel;
        }

        /// <summary>
        /// Update Items. Used for days
        /// </summary>
        /// <param name="dateTime">
        /// The date Time.
        /// </param>
        internal void CreateOrUpdateItems(DateTime dateTime)
        {
            if (this.Items == null)
            {
                return;
            }

            DateTimeWrapper selectedDateTimeWrapper = null;
            int newMax;
            var firstAvailableDate = this.GetFirstAvailable(dateTime, out newMax);

            // Make a copy without any minutes / seconds ...
            var newData =
                new DateTimeWrapper(
                    new DateTime(firstAvailableDate.Year, firstAvailableDate.Month, firstAvailableDate.Day));

            // One item is deleted but was selected..
            // Don't forget to reactivate a selected item
            var oneItemMustBeDeletedAndIsSelected = false;

            // If new month have less day than last month
            var itemCollection = this.Items;

            if (itemCollection != null && newMax < itemCollection.Count)
            {
                var collection = this.Items;
                if (collection != null)
                {
                    var numberOfLastDaysToDelete = collection.Count - newMax;
                    for (var cpt = 0; cpt < numberOfLastDaysToDelete; cpt++)
                    {
                        var item =
                            this.ItemContainerGenerator.ContainerFromItem(collection[collection.Count - 1]) as
                            PickerSelectorItem;

                        if (item == null)
                        {
                            continue;
                        }

                        if (item.IsSelected)
                        {
                            oneItemMustBeDeletedAndIsSelected = true;
                        }

                        collection.RemoveAt(collection.Count - 1);
                    }
                }
            }

            for (var i = 0; i < newMax; i++)
            {
                // -----------------------------------------------------------------------------
                // Add or Update Items
                // -----------------------------------------------------------------------------
                var collection = this.Items;
                if (collection != null && collection.Count <= i)
                {
                    var items = this.Items;
                    if (items != null)
                    {
                        items.Add(newData);
                    }
                }
                else
                {
                    // Verify the item already exists
                    var items = this.Items;
                    if (items != null)
                    {
                        var itemDate = ((DateTimeWrapper)items[i]).DateTime;

                        if (itemDate != newData.DateTime)
                        {
                            items[i] = newData;
                        }
                    }
                }

                // -----------------------------------------------------------------------------
                // Get the good selected itm
                // -----------------------------------------------------------------------------
                if (newData.DateTime == dateTime)
                {
                    selectedDateTimeWrapper = newData;
                }

                // -----------------------------------------------------------------------------
                // Get the next data, relative to original wrapper, then relative to firstWrapper
                // -----------------------------------------------------------------------------
                DateTime? nextData = null;

                // Get nex date
                switch (this.DataSourceType)
                {
                    case DataSourceType.Year:
                        nextData = this.YearDataSource.GetNext(dateTime, firstAvailableDate, i + 1);
                        break;
                    case DataSourceType.Month:
                        nextData = this.MonthDataSource.GetNext(dateTime, firstAvailableDate, i + 1);
                        break;
                    case DataSourceType.Day:
                        nextData = this.DayDataSource.GetNext(dateTime, firstAvailableDate, i + 1);
                        break;
                }

                if (nextData == null)
                {
                    break;
                }

                newData = nextData.Value.ToDateTimeWrapper();
            }

            // Set the correct Selected Item
            if (selectedDateTimeWrapper != null)
            {
                this.SelectedItem = selectedDateTimeWrapper;
            }
            else if (oneItemMustBeDeletedAndIsSelected)
            {
                // When 31 was selected and we are on a Month < 31 days (February, April ...)
                var collection = this.Items;
                if (collection != null)
                {
                    this.SelectedItem = (DateTimeWrapper)collection[collection.Count - 1];
                }
            }
            else
            {
                var collection = this.Items;
                if (collection != null)
                {
                    this.SelectedItem = (DateTimeWrapper)collection[0];
                }
            }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// If Tapped on selected item, prevent animation and visual states
        /// </summary>
        /// <param name="e">
        /// The e.
        /// </param>
        [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
            Justification = "Reviewed. Suppression is OK here.")]
        protected override void OnTapped(TappedRoutedEventArgs e)
        {
            if (!this.IsFocused && this.DatePicker.EnableFirstTapHasFocusOnly)
            {
                return;
            }

            var fxElement = e.OriginalSource as FrameworkElement;

            if (fxElement == null)
            {
                return;
            }

            var dtw = fxElement.DataContext as DateTimeWrapper;

            if (dtw == null)
            {
                return;
            }

            if (this.SelectedItem != null && this.SelectedItem.DateTime == dtw.DateTime)
            {
                return;
            }

            this.SelectedItem = dtw;
        }

        /// <summary>
        ///     Overridden. Creates or identifies the element that is used to display the given item.
        /// </summary>
        /// <returns>
        ///     The <see cref="DependencyObject" />.
        /// </returns>
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new PickerSelectorItem
            {
                PickerSelector = this
            };
        }

        /// <summary>
        /// Prepares the specified element to display the specified item.
        /// </summary>
        /// <param name="element">
        /// The element.
        /// </param>
        /// <param name="item">
        /// The item.
        /// </param>
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            if (DesignMode.DesignModeEnabled)
            {
                return;
            }

            this.itemsPanel = this.GetVisualDescendent<LoopItemsPanel>();

            // get the item
            var loopListItem = element as PickerSelectorItem;
            var dateTimeWrapper = item as DateTimeWrapper;

            if (loopListItem == null || dateTimeWrapper == null)
            {
                return;
            }

            if (this.ItemTemplate == null)
            {
                return;
            }

            // load data templated
            var contentElement = this.ItemTemplate.LoadContent() as FrameworkElement;

            if (contentElement == null)
            {
                return;
            }

            // attach DataContext and Context to the item
            loopListItem.Style = this.ItemContainerStyle;
            loopListItem.DataContext = item;
            loopListItem.Content = contentElement;
            loopListItem.IsSelected = dateTimeWrapper == this.SelectedItem;
            loopListItem.IsFocused = this.IsFocused;
        }

        /// <summary>
        /// Maybe this method is Obsolet : TODO : Test obsoletence
        /// </summary>
        /// <param name="e">
        /// The obj.
        /// </param>
        protected override void OnManipulationCompleted(ManipulationCompletedRoutedEventArgs e)
        {
            var middleItem = this.itemsPanel.GetMiddleItem();

            if (middleItem == null)
            {
                return;
            }

            this.SelectedItem = middleItem.DataContext as DateTimeWrapper;

            base.OnManipulationCompleted(e);
        }

        /// <summary>
        /// Check item type
        /// </summary>
        /// <param name="item">
        /// The item.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is PickerSelectorItem;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// When focus is set, just set IsFocused on the itemsPanel
        /// </summary>
        /// <param name="d">
        /// The d.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private static void OnIsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue == null || (e.NewValue == e.OldValue))
            {
                return;
            }

            var pickerSelector = (PickerSelector)d;

            var isFocused = (bool)e.NewValue;

            pickerSelector.UpdateIsFocusedItems(isFocused);
        }

        /// <summary>
        /// Update is focused or not on items state
        /// </summary>
        /// <param name="isFocused">
        /// The is Focused.
        /// </param>
        private void UpdateIsFocusedItems(bool isFocused)
        {
            if (this.Items == null || this.Items.Count <= 0)
            {
                return;
            }

            if (this.itemsPanel == null)
            {
                return;
            }

            // ReSharper disable once PossibleInvalidCastExceptionInForeachLoop
            foreach (PickerSelectorItem child in this.itemsPanel.Children)
            {
                child.IsFocused = isFocused;
            }
        }

        /// <summary>
        /// Update selected or not selected items state
        /// </summary>
        /// <param name="selectedValue">
        /// The selected Value.
        /// </param>
        private void UpdateIsSelectedItems(DateTimeWrapper selectedValue)
        {
            if (this.Items == null || this.Items.Count <= 0)
            {
                return;
            }

            if (this.itemsPanel == null)
            {
                return;
            }

            if (selectedValue == null)
            {
                return;
            }

            foreach (var uiElement in this.itemsPanel.Children)
            {
                var pickerSelectorItem = (PickerSelectorItem)uiElement;
                var currentValue = (DateTimeWrapper)pickerSelectorItem.DataContext;
                pickerSelectorItem.IsSelected = selectedValue.DateTime == currentValue.DateTime;
                if (pickerSelectorItem.IsSelected)
                {
                    this.selectedPickerSelectorItem = pickerSelectorItem;
                }
            }
        }

        /// <summary>
        /// Get the first available date and the max (28, 29 30 or 31 for Day per example)
        /// </summary>
        /// <param name="dateTime">
        /// The date Time.
        /// </param>
        /// <param name="newMax">
        /// The new Max.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        private DateTime GetFirstAvailable(DateTime dateTime, out int newMax)
        {
            var firstAvailableDate = dateTime;
            newMax = 0;
            switch (this.DataSourceType)
            {
                case DataSourceType.Year:
                    firstAvailableDate = this.YearDataSource.GetFirstAvailableDate(dateTime);
                    newMax = this.YearDataSource.GetNumberOfItems(dateTime);
                    break;
                case DataSourceType.Month:
                    firstAvailableDate = this.MonthDataSource.GetFirstAvailableDate(dateTime);
                    newMax = this.MonthDataSource.GetNumberOfItems(dateTime);
                    break;
                case DataSourceType.Day:
                    firstAvailableDate = this.DayDataSource.GetFirstAvailableDate(dateTime);
                    newMax = this.DayDataSource.GetNumberOfItems(dateTime);
                    break;
            }

            return firstAvailableDate;
        }

        #endregion
    }
}